#include "STips.h"

#include <core/StyleMgr.h>

#include <QApplication>
#include <QDesktopWidget>
#include <QEvent>
#include <QMouseEvent>
#include <QScreen>
#include <QStyle>
#include <QStyleOption>
#include <QStylePainter>
#include <QTextDocument>

namespace qb {
Q_GLOBAL_STATIC(QPalette, tooltip_palette)
QBTipLabel* QBTipLabel::instance = nullptr;

QBTipLabel::QBTipLabel(const QString& text, const QPoint& pos, QWidget* w,
                       int msecDisplayTime, bool html)
    : QLabel(w, Qt::ToolTip | Qt::BypassGraphicsProxyWidget),
      widget(nullptr),
      m_html(html) {
  static bool s_init_palette = false;
  if (!s_init_palette) {
    tooltip_palette()->setColor(
        QPalette::All, QPalette::ToolTipBase,
        StyleMgr::instance().color("BackNormal"));  // 设置ToolTip背景色
    tooltip_palette()->setColor(
        QPalette::All, QPalette::ToolTipText,
        StyleMgr::instance().color("TextNormal"));  // 设置ToolTip字体色
    s_init_palette = true;
  }
  delete instance;
  instance = this;
  setForegroundRole(QPalette::ToolTipText);
  setBackgroundRole(QPalette::ToolTipBase);
  setPalette(QBTips::palette());
  setWordWrap(true);
  ensurePolished();
  setMargin(1 + style()->pixelMetric(QStyle::PM_ToolTipLabelFrameWidth, nullptr,
                                     this));
  setFrameStyle(QFrame::NoFrame);
  setAlignment(Qt::AlignLeft);
  setIndent(1);
  qApp->installEventFilter(this);
  setWindowOpacity(
      style()->styleHint(QStyle::SH_ToolTipLabel_Opacity, nullptr, this) /
      255.0);
  setMouseTracking(true);
  fadingOut = false;
  border = StyleMgr::instance().color("TipsBorder");
  reuseTip(text, msecDisplayTime, pos);
}

void QBTipLabel::restartExpireTimer(int msecDisplayTime) {
  if (-1 == msecDisplayTime) {
    hideTimer.stop();
    return;
  }

  int time = 10000 + 40 * qMax(0, text().length() - 100);
  if (msecDisplayTime > 0) time = msecDisplayTime;
  expireTimer.start(time, this);
}

void QBTipLabel::reuseTip(const QString& text, int msecDisplayTime,
                          const QPoint& pos) {
  setText(text);
  updateSize(pos);
  restartExpireTimer(msecDisplayTime);
}

void QBTipLabel::updateSize(const QPoint& pos) {
  // Ensure that we get correct sizeHints by placing this window on the right
  // screen.
  QFontMetrics fm(font());
  QSize extra(1, 0);
  // Make it look good with the default ToolTip font on Mac, which has a small
  // descent.
  if (fm.descent() == 2 && fm.ascent() >= 11) ++extra.rheight();
  setWordWrap(Qt::mightBeRichText(text()));
  QSize sh = sizeHint();
  // ### When the above WinRT code is fixed, windowhandle should be used to find
  // the screen.
  QScreen* screen = QGuiApplication::screenAt(pos);
  if (!screen) screen = QGuiApplication::primaryScreen();
  if (screen) {
    const qreal screenWidth = screen->geometry().width();
    if (!wordWrap() && sh.width() > screenWidth) {
      setWordWrap(true);
      sh = sizeHint();
    }
  }
  resize(sh + extra);
}

void QBTipLabel::paintEvent(QPaintEvent* ev) {
  if (m_html) return QLabel::paintEvent(ev);

  QStylePainter p(this);
  QStyleOptionFrame opt;

  QRect crAll = contentsRect();
  QRect cr = crAll;
  cr.adjust(margin(), margin(), -margin(), -margin());

  opt.init(this);
  p.fillRect(crAll, QBTips::palette().toolTipBase());

  QPen oldPen = p.pen();
  p.setPen(border);
  p.drawRect(0, 0, crAll.width() - 1, crAll.height() - 1);
  p.setPen(oldPen);

  QRectF rect(0, 0, 10000, 10000);
  int nFlag = Qt::TextDontClip | Qt::AlignLeft | Qt::AlignVCenter;
  QRectF spaaceRect = p.boundingRect(rect, nFlag, " ");

  QStringList rlist = text().split('\n');
  int rSize = rlist.size();
  for (int i = 0; i < rSize; i++) {
    int nLastWidth = 0;
    QStringList clist = rlist.at(i).split(' ');
    for (int j = 0; j < clist.size(); j++) {
      QPen oldPen;
      if (tipItems[i][j].bCustom) {
        oldPen = p.pen();
        p.setPen(tipItems[i][j].clrNormal);
        if (tipItems[i][j].font) {
          p.setFont(*tipItems[i][j].font);
        }
      }

      QRectF measureRect = p.boundingRect(rect, nFlag, clist.at(j));
      p.drawText(cr.left() + nLastWidth, cr.top() + cr.height() / rSize * i,
                 cr.width(), cr.height() / rSize, nFlag, clist.at(j));

      if (tipItems[i][j].bCustom) {
        p.setPen(oldPen);
        if (tipItems[i][j].font) {
          p.setFont(font());
        }
      }

      nLastWidth += measureRect.width() + spaaceRect.width();
    }
  }

  //	p.drawPrimitive(QStyle::PE_PanelTipLabel, opt);
  p.end();

  //	QLabel::paintEvent(ev);
}

void QBTipLabel::resizeEvent(QResizeEvent* e) {
  QStyleHintReturnMask frameMask;
  QStyleOption option;
  option.init(this);
  if (style()->styleHint(QStyle::SH_ToolTip_Mask, &option, this, &frameMask))
    setMask(frameMask.region);

  QLabel::resizeEvent(e);
}

void QBTipLabel::mouseMoveEvent(QMouseEvent* e) {
  if (!rect.isNull()) {
    QPoint pos = e->globalPos();
    if (widget) pos = widget->mapFromGlobal(pos);
    if (!rect.contains(pos)) hideTip();
  }
  QLabel::mouseMoveEvent(e);
}

QBTipLabel::~QBTipLabel() { instance = nullptr; }

void QBTipLabel::hideTip() {
  if (!hideTimer.isActive()) hideTimer.start(300, this);
}

void QBTipLabel::hideTipImmediately() {
  close();  // to trigger QEvent::Close which stops the animation
  deleteLater();
}

void QBTipLabel::setTipRect(QWidget* w, const QRect& r) {
  if (Q_UNLIKELY(!r.isNull() && !w)) {
    qWarning("QToolTip::setTipRect: Cannot pass null widget if rect is set");
    return;
  }
  widget = w;
  rect = r;
}

void QBTipLabel::timerEvent(QTimerEvent* e) {
  if (e->timerId() == hideTimer.timerId() ||
      e->timerId() == expireTimer.timerId()) {
    hideTimer.stop();
    expireTimer.stop();
    hideTipImmediately();
  }
}

bool QBTipLabel::eventFilter(QObject* o, QEvent* e) {
  switch (e->type()) {
#ifdef CCORE_MAC
    case QEvent::KeyPress:
    case QEvent::KeyRelease: {
      const int key = static_cast<QKeyEvent*>(e)->key();
      // Anything except key modifiers or caps-lock, etc.
      if (key < Qt::Key_Shift || key > Qt::Key_ScrollLock) hideTipImmediately();
      break;
    }
#endif
    case QEvent::Leave:
      hideTip();
      break;

    case QEvent::WindowActivate:
    case QEvent::WindowDeactivate:
    case QEvent::FocusIn:
    case QEvent::FocusOut:
    case QEvent::Close:  // For QTBUG-55523 (QQC) specifically: Hide tooltip
                         // when windows are closed
    case QEvent::MouseButtonPress:
    // case QEvent::MouseButtonRelease: //固收综合屏幕setting处spinBox
    // release不hide tips
    case QEvent::MouseButtonDblClick:
    case QEvent::Wheel:
      hideTipImmediately();
      break;

    case QEvent::MouseMove:
      if (o == widget && !rect.isNull() &&
          !rect.contains(static_cast<QMouseEvent*>(e)->pos()))
        hideTip();
    default:
      break;
  }
  return false;
}

int QBTipLabel::getTipScreen(const QPoint& pos, QWidget* w) {
  QT_WARNING_PUSH
  QT_WARNING_DISABLE_DEPRECATED
  QDesktopWidget* d = QApplication::desktop();
  if (d->isVirtualDesktop())
    return d->screenNumber(pos);
  else
    return d->screenNumber(w);
  QT_WARNING_POP
}

void QBTipLabel::placeTip(const QPoint& pos, QWidget* w) {
  QPoint p = pos;
  const QScreen* screen = QGuiApplication::screens().value(
      getTipScreen(pos, w), QGuiApplication::primaryScreen());
  // a QScreen's handle *should* never be null, so this is a bit paranoid
  if (const QPlatformScreen* platformScreen =
          screen ? screen->handle() : nullptr) {
    //     QPlatformCursor* cursor = platformScreen->cursor();
    // default implementation of QPlatformCursor::size() returns QSize(16, 16)
    const QSize nativeSize = QSize(16, 16);
    const QSize cursorSize = nativeSize;
    QPoint offset(10, cursorSize.height() + 2);
    // assuming an arrow shape, we can just move to the side for very large
    // cursors
    if (cursorSize.height() > 2 * this->height())
      offset = QPoint(cursorSize.width() / 2, 0);

    p += offset;

    QRect screenRect = screen->geometry();
    if (p.x() + this->width() > screenRect.x() + screenRect.width())
      p.rx() -= 4 + this->width();
    if (p.y() + this->height() > screenRect.y() + screenRect.height())
      p.ry() -= 24 + this->height();
    if (p.y() < screenRect.y()) p.setY(screenRect.y());
    if (p.x() + this->width() > screenRect.x() + screenRect.width())
      p.setX(screenRect.x() + screenRect.width() - this->width());
    if (p.x() < screenRect.x()) p.setX(screenRect.x());
    if (p.y() + this->height() > screenRect.y() + screenRect.height())
      p.setY(screenRect.y() + screenRect.height() - this->height());
  }
  this->move(p);
}

void QBTipLabel::clearCustomItem() {
  for (int i = 0; i < QBTIP_MAX_ITEM_COUNT; i++) {
    for (int j = 0; j < QBTIP_MAX_ITEM_COUNT; j++) {
      tipItems[i][j].bCustom = false;
      tipItems[i][j].font = nullptr;
    }
  }
}

void QBTipLabel::setCustomItem(int row, int col, QColor clrNormal,
                               QFont* font) {
  if (row < 0 || row >= QBTIP_MAX_ITEM_COUNT || col < 0 ||
      col >= QBTIP_MAX_ITEM_COUNT) {
    return;
  }

  tipItems[row][col].bCustom = true;
  tipItems[row][col].clrNormal = clrNormal;
  tipItems[row][col].font = font;
}

void QBTipLabel::setBorderColor(QColor clrBorder) { border = clrBorder; }

void QBTipLabel::setHtml(bool v) {
  m_html = v;
  updateSize({0, 0});
  update();
}

bool QBTipLabel::tipChanged(const QPoint& pos, const QString& text,
                            QObject* o) {
  if (QBTipLabel::instance->text() != text) return true;

  if (o != widget) return true;

  if (!rect.isNull())
    return !rect.contains(pos);
  else
    return false;
}

void QBTips::showText(const QPoint& pos, const QString& text, QWidget* w,
                      std::vector<QBTipCustomItem>* vctCustomItem,
                      bool followMouse, bool html, int margin, int msecShowTime,
                      QColor border) {
  QRect rect;
  if (QBTipLabel::instance &&
      QBTipLabel::instance->isVisible()) {  // a tip does already exist
    if (text.isEmpty()) {                   // empty text means hide current tip
      QBTipLabel::instance->hideTip();
      return;
    } else if (!QBTipLabel::instance->fadingOut) {
      // If the tip has changed, reuse the one
      // that is showing (removes flickering)
      QPoint localPos = pos;
      bool bShow = false;
      if (followMouse)
        bShow = true;
      else {
        if (w) localPos = w->mapFromGlobal(pos);
        bShow = QBTipLabel::instance->tipChanged(localPos, text, w);
      }
      if (bShow) {
        QBTipLabel::instance->clearCustomItem();
        if (vctCustomItem) {
          for (size_t i = 0; i < vctCustomItem->size(); i++) {
            QBTipLabel::instance->setCustomItem(
                (*vctCustomItem)[i].row, (*vctCustomItem)[i].col,
                (*vctCustomItem)[i].clrNormal, (*vctCustomItem)[i].font);
          }
        }
        QBTipLabel::instance->setBorderColor(border);
        QBTipLabel::instance->setMargin(margin);
        QBTipLabel::instance->reuseTip(text, msecShowTime, pos);
        QBTipLabel::instance->setTipRect(w, rect);
        QBTipLabel::instance->placeTip(pos, w);
        QBTipLabel::instance->setHtml(html);
      } else
        QBTipLabel::instance->placeTip(pos, w);
      return;
    }
  }

  if (!text.isEmpty()) {  // no tip can be reused, create new tip:
#ifdef Q_OS_WIN32
    // On windows, we can't use the widget as parent otherwise the window will
    // be raised when the tooltip will be shown
    QT_WARNING_PUSH
    QT_WARNING_DISABLE_DEPRECATED
    new QBTipLabel(
        text, pos,
        QApplication::desktop()->screen(QBTipLabel::getTipScreen(pos, w)),
        msecShowTime, html);
    QT_WARNING_POP
#else
    new QBTipLabel(text, pos, w,
                   msecShowTime);  // sets QTipLabel::instance to itself
#endif
    if (vctCustomItem) {
      for (size_t i = 0; i < vctCustomItem->size(); i++) {
        QBTipLabel::instance->setCustomItem(
            (*vctCustomItem)[i].row, (*vctCustomItem)[i].col,
            (*vctCustomItem)[i].clrNormal, (*vctCustomItem)[i].font);
      }
    }
    QBTipLabel::instance->setBorderColor(border);
    QBTipLabel::instance->setMargin(margin);
    QBTipLabel::instance->setTipRect(w, rect);
    QBTipLabel::instance->placeTip(pos, w);
    QBTipLabel::instance->setObjectName(QLatin1String("qtooltip_label"));
    QBTipLabel::instance->showNormal();
    QBTipLabel::instance->setHtml(html);
  }
}

void QBTips::hideTip() {
  if (QBTipLabel::instance) QBTipLabel::instance->hideTipImmediately();
}

bool QBTips::isVisible() {
  return (QBTipLabel::instance != nullptr && QBTipLabel::instance->isVisible());
}

QString QBTips::text() {
  if (QBTipLabel::instance) return QBTipLabel::instance->text();
  return QString();
}

QPalette QBTips::palette() { return *tooltip_palette(); }

void QBTips::setPalette(const QPalette& palette) {
  *tooltip_palette() = palette;
  if (QBTipLabel::instance) QBTipLabel::instance->setPalette(palette);
}

QFont QBTips::font() { return QApplication::font("QTipLabel"); }

void QBTips::setFont(const QFont& font) {
  QApplication::setFont(font, "QBTipLabel");
}
}  // namespace qb